package org.ccay.log.webtrace.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.filter.ThreadContextMapFilter;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.ccay.core.util.StringUtil;
import org.ccay.log.LoggerInfoVO;

/**
 * Log级别调整工具类
 * 
 * @author chaowangbang
 * 
 */
public final class LogLevelSwitcher {
	private LogLevelSwitcher() {
	}

	private static LogLevelSwitcher instance = new LogLevelSwitcher();

	// 实例化构造函数指定参数false,不然动态修改日志级别不起作用
	private static LoggerContext ctx = (LoggerContext) LogManager.getContext(false);

	/**
	 * 获取唯一实例
	 * 
	 * @return
	 */
	public static LogLevelSwitcher getInstance() {
		return instance;
	}

	// 修改过的日志集合
	private Map<String, String> updatedLoggerList = new HashMap<String, String>();

	/**
	 * 根据loggerName查找单个日志对象
	 * 
	 * @param loggerName
	 * @return
	 */
	public LoggerInfoVO fingLogger(String loggerName) {
		LoggerConfig log4jLogger = findOriginalLogger(loggerName);

		if (null != log4jLogger) {
			return buildLoggerInfo(log4jLogger);
		}

		return null;
	}

	/**
	 * 查找所有日志对象
	 * 
	 * @return
	 */
	public List<LoggerInfoVO> findAllLoggers() {
		ArrayList<LoggerInfoVO> loggerList = new ArrayList<LoggerInfoVO>();

		for (LoggerConfig logger : ctx.getConfiguration().getLoggers().values()) {
			LoggerInfoVO log = buildLoggerInfo(logger);

			if (null != log) {
				loggerList.add(log);
			}
		}

		Collections.sort(loggerList, new Comparator<LoggerInfoVO>() {
			public int compare(LoggerInfoVO o1, LoggerInfoVO o2) {
				return o1.getLoggerName() == null ? -1 : o1.getLoggerName().compareTo(o2.getLoggerName());
			}
		});

		return loggerList;
	}

	/**
	 * 查找原始Log4j Logger
	 * 
	 * @param name
	 * @return
	 */
	private static LoggerConfig findOriginalLogger(String name) {
		if ("root".equalsIgnoreCase(name)) {
			return ctx.getConfiguration().getLoggerConfig(LogManager.ROOT_LOGGER_NAME);
		}

		return ctx.getConfiguration().getLoggers().get(name);
	}

	/**
	 * 根据Log4jLogger构建LoggerInfoVO
	 * 
	 * @param log4jLogger
	 * @return
	 */
	private LoggerInfoVO buildLoggerInfo(LoggerConfig log4jLogger) {
		if (null == log4jLogger.getLevel() || null == log4jLogger.getAppenders()
				|| log4jLogger.getAppenders().isEmpty()) {
			return null;
		}

		LoggerInfoVO logger = new LoggerInfoVO();

		if (updatedLoggerList.containsKey(log4jLogger.getName())) {
			logger.setOriginalLevel(updatedLoggerList.get(log4jLogger.getName()));
		} else {
			logger.setOriginalLevel(log4jLogger.getLevel().toString());
		}

		logger.setLoggerName(StringUtils.isEmpty(log4jLogger.getName()) ? "root" : log4jLogger.getName());
		logger.setLevel(log4jLogger.getLevel().toString());

		StringBuilder appenders = new StringBuilder();
		StringBuilder fileNames = new StringBuilder();

		for (Appender appRef : log4jLogger.getAppenders().values()) {
			if (appRef instanceof RollingFileAppender) {
				RollingFileAppender rollingFileAppender = (RollingFileAppender) appRef;
				appenders.append(rollingFileAppender.getName()).append(", ");
				fileNames.append(rollingFileAppender.getFileName()).append(", ");
			}
		}
		logger.setFileName(fileNames.toString());
		logger.setAppender(appenders.toString());

		return logger;
	}

	/**
	 * 调整级别，并在必要时启动或者关闭WebTrace
	 * 
	 * @param logger
	 */
	public synchronized void switchLevel(LoggerInfoVO logger) {
		if (StringUtil.isNullOrEmpty(logger.getLevel())) {
			return;
		}
		
		String loggerName = logger.getLoggerName();

		LoggerConfig log4jLogger = findOriginalLogger(loggerName);
		String originalLevel = updatedLoggerList.get(loggerName);

		if (StringUtils.isEmpty(originalLevel)) {
			originalLevel = log4jLogger.getLevel().toString();
		}

		if (logger.getLevel().equals(originalLevel)) {
			// 且原始级别与目标级别相等，则视为恢复原始级别
			restoreOriginalLevel(logger, loggerName, log4jLogger);
			return;
		}

		Level toLevel = Level.toLevel(logger.getLevel());

		// 低于info级别开启WebTrace
		if (toLevel.isLessSpecificThan(Level.INFO)) {
			beginTrace(logger, loggerName, log4jLogger);
			return;
		}
		saveAndSwitch(logger, loggerName, log4jLogger);
	}

	/**
	 * 保存原始级别，并调整到新级别
	 * 
	 * @param logger
	 * @param loggerName
	 * @param log4jLogger
	 */
	private void saveAndSwitch(LoggerInfoVO logger, String loggerName, LoggerConfig log4jLogger) {
		if (!updatedLoggerList.containsKey(loggerName)) {
			updatedLoggerList.put(loggerName, log4jLogger.getLevel().toString());
		}
		log4jLogger.setLevel(Level.toLevel(logger.getLevel()));
		// 通知所有记录仪从他们LoggerConfig重新读取信息
		ctx.updateLoggers();
	}

	/**
	 * 开始Trace
	 * 
	 * @param logger
	 * @param loggerName
	 * @param log4jLogger
	 */
	private void beginTrace(LoggerInfoVO logger, String loggerName, LoggerConfig log4jLogger) {
		// 不等于原始类型，此处开始加过滤器
		addWebTraceFilter(log4jLogger, logger.getTraceUserAccount());

		saveAndSwitch(logger, loggerName, log4jLogger);

		WebTraceStatusManager.getInstance().beginTrace(logger.getTraceUserAccount());
	}

	/**
	 * 恢复到原始级别，必要时终止WebTrace
	 * 
	 * @param loggerName
	 * @param log4jLogger
	 */
	private void restoreOriginalLevel(LoggerInfoVO logger, String loggerName, LoggerConfig log4jLogger) {
		log4jLogger.setLevel(Level.toLevel(logger.getLevel()));
		ctx.updateLoggers();

		updatedLoggerList.remove(loggerName);
		// 如果全部恢复，则终止Trace
		if (updatedLoggerList.isEmpty()) {
			// 等于原始类型，没有其他要跟踪的日志类型 此处恢复所有过滤器
			clearWebTraceFilters(log4jLogger);
			WebTraceStatusManager.getInstance().endTrace();
		}
	}

	/**
	 * 清理过滤器
	 * 
	 * @param logger
	 */
	private void addWebTraceFilter(LoggerConfig logger, String userAccount) {
		if (!StringUtil.isNullOrEmpty(userAccount) && !"null".equals(userAccount)) {
			KeyValuePair[] pairs = new KeyValuePair[] { new KeyValuePair("userAccount",
					userAccount.toLowerCase(Locale.ENGLISH)) };
			ThreadContextMapFilter filter = ThreadContextMapFilter.createFilter(pairs, null, null, null);
			logger.addFilter(filter);
			ctx.updateLoggers();
		}
	}

	/**
	 * 清理过滤器
	 * 
	 * @param logger
	 */
	private void clearWebTraceFilters(LoggerConfig logger) {
		for (LoggerConfig loggerConfig : ctx.getConfiguration().getLoggers().values()) {
			Filter filter = loggerConfig.getFilter();
			if (null != filter) {
				if (filter instanceof ThreadContextMapFilter) {
					loggerConfig.removeFilter(filter);
				}
			}
		}
		ctx.updateLoggers();
	}
}